package org.sofun.subscription.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sofun.subscription.enums.DayType;
import org.sofun.subscription.enums.OffsetType;
import org.sofun.subscription.enums.OpenDayOrder;
import org.sofun.subscription.enums.TradeFrequency;
import org.sofun.subscription.model.DateRange;
import org.sofun.subscription.model.FundPeriod;
import org.sofun.subscription.model.SubscriptionRule;
import org.sofun.subscription.util.HolidayUtil;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 申购服务类
 */
@Service
public class SubscriptionService {

    private static final Logger logger = LoggerFactory.getLogger(SubscriptionService.class);
    
    /**
     * 生成申购开放日列表
     * @param rule 申购规则参数
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @param periodCount 生成期数（可选，与endDate二选一）
     * @return 开放日列表
     */
    public List<LocalDate> generateOpenDays(SubscriptionRule rule, LocalDate startDate,
                                           LocalDate endDate, Integer periodCount) {
        return generateOpenDays(rule, startDate, endDate, periodCount, LocalDate.now());
    }

    /**
     * 生成申购开放日列表（可指定当前日期，主要用于测试）
     * @param rule 申购规则参数
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @param periodCount 生成期数（可选，与endDate二选一）
     * @param currentDate 当前日期（用于过期检查）
     * @return 开放日列表
     */
    public List<LocalDate> generateOpenDays(SubscriptionRule rule, LocalDate startDate,
                                           LocalDate endDate, Integer periodCount, LocalDate currentDate) {

        logger.info("开始生成申购开放日列表 - 规则: {}, 开始日期: {}, 结束日期: {}, 期数: {}, 当前日期: {}",
                   rule.getTradeFrequency(), startDate, endDate, periodCount, currentDate);

        // 1. 参数校验
        validateParameters(rule, startDate, endDate, periodCount);
        logger.debug("参数校验通过");

        // 2. 确定生成范围
        LocalDate actualEndDate = determineEndDate(startDate, endDate, periodCount, rule.getTradeFrequency());
        logger.debug("确定实际结束日期: {}", actualEndDate);

        // 3. 按频率划分周期并生成开放日
        List<LocalDate> openDays = new ArrayList<>();
        LocalDate currentPeriodStart = startDate;
        
        while (currentPeriodStart.isBefore(actualEndDate) || currentPeriodStart.isEqual(actualEndDate)) {
            try {
                logger.debug("处理周期: {}", currentPeriodStart);

                // 3.1 计算当前周期范围
                DateRange periodRange = calculatePeriodRange(currentPeriodStart, rule.getTradeFrequency());
                logger.debug("周期范围: {} - {}", periodRange.getStart(), periodRange.getEnd());

                // 3.2 计算周期内目标开放日
                LocalDate targetOpenDay = calculateTargetOpenDay(periodRange, rule);
                logger.debug("目标开放日: {}", targetOpenDay);

                // 3.3 根据日类型调整开放日
                LocalDate adjustedOpenDay = adjustOpenDayByType(targetOpenDay, periodRange, rule.getOpenDayType());
                logger.debug("调整后开放日: {}", adjustedOpenDay);

                // 3.4 检查开放日是否有效（未来日期且在结束日期之前）
                if (adjustedOpenDay != null &&
                    adjustedOpenDay.isAfter(currentDate) &&
                    !adjustedOpenDay.isAfter(actualEndDate)) {
                    openDays.add(adjustedOpenDay);
                    logger.info("添加开放日: {}", adjustedOpenDay);
                } else if (adjustedOpenDay != null && !adjustedOpenDay.isAfter(currentDate)) {
                    logger.debug("跳过已过期的开放日: {} (当前日期: {})", adjustedOpenDay, currentDate);
                }

                // 3.5 移动到下一个周期
                currentPeriodStart = getNextPeriodStart(currentPeriodStart, rule.getTradeFrequency());
            } catch (Exception e) {
                // 如果某个周期计算失败，记录日志并继续下一个周期
                logger.error("计算周期 {} 的开放日时发生异常: {}", currentPeriodStart, e.getMessage(), e);
                currentPeriodStart = getNextPeriodStart(currentPeriodStart, rule.getTradeFrequency());
            }
        }

        logger.info("申购开放日生成完成，共生成 {} 个开放日: {}", openDays.size(), openDays);
        return openDays;
    }
    
    /**
     * 计算基金期间列表（募集期起止日）
     * @param openDays 开放日列表
     * @param rule 申购规则参数
     * @return 基金期间列表
     */
    public List<FundPeriod> calculateFundPeriods(List<LocalDate> openDays, SubscriptionRule rule) {

        logger.info("开始计算基金期间列表 - 开放日数量: {}, 初始日期: {}",
                   openDays != null ? openDays.size() : 0, rule.getInitialDateTime());

        // 1. 参数校验
        if (openDays == null || openDays.isEmpty()) {
            logger.error("开放日列表为空");
            throw new IllegalArgumentException("开放日列表不能为空");
        }
        if (rule.getInitialDateTime() == null) {
            logger.error("第一期募集期起始日为空");
            throw new IllegalArgumentException("第一期募集期起始日不能为空");
        }

        // 2. 按开放日排序
        List<LocalDate> sortedOpenDays = new ArrayList<>(openDays);
        Collections.sort(sortedOpenDays);
        logger.debug("开放日排序完成: {}", sortedOpenDays);

        // 3. 计算每期的募集期间
        List<FundPeriod> fundPeriods = new ArrayList<>();
        LocalDateTime previousCloseDateTime = rule.getInitialDateTime();
        
        for (int i = 0; i < sortedOpenDays.size(); i++) {
            LocalDate openDay = sortedOpenDays.get(i);
            logger.debug("处理第 {} 期开放日: {}", i + 1, openDay);

            // 3.1 计算募集期结束日
            LocalDateTime closeDateTime = calculateCloseDateTime(openDay, rule);
            logger.debug("第 {} 期募集期结束日: {}", i + 1, closeDateTime);

            // 3.2 设置募集期起始日
            LocalDateTime startDateTime = (i == 0) ? rule.getInitialDateTime() : previousCloseDateTime;
            logger.debug("第 {} 期募集期起始日: {}", i + 1, startDateTime);

            // 3.3 生成订单归集规则描述
            String ruleDescription = generateRuleDescription(startDateTime, closeDateTime, openDay);
            logger.debug("第 {} 期归集规则: {}", i + 1, ruleDescription);

            // 3.4 创建基金期间对象
            FundPeriod fundPeriod = new FundPeriod(openDay, startDateTime, closeDateTime, ruleDescription);
            fundPeriods.add(fundPeriod);
            logger.info("添加第 {} 期基金期间: 开放日={}, 募集期={}~{}",
                       i + 1, openDay, startDateTime, closeDateTime);

            // 3.5 更新上一期结束时间
            previousCloseDateTime = closeDateTime;
        }

        logger.info("基金期间计算完成，共生成 {} 期", fundPeriods.size());
        return fundPeriods;
    }
    
    /**
     * 带异常处理的开放日生成方法
     */
    public List<LocalDate> generateOpenDaysWithExceptionHandling(SubscriptionRule rule,
                                                               LocalDate startDate,
                                                               LocalDate endDate,
                                                               Integer periodCount) {
        try {
            logger.info("调用带异常处理的开放日生成方法");
            return generateOpenDays(rule, startDate, endDate, periodCount);
        } catch (IllegalArgumentException e) {
            // 参数异常：直接抛出
            logger.error("参数校验失败: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            // 其他异常：包装后抛出
            logger.error("生成申购开放日时发生未知异常", e);
            throw new RuntimeException("生成申购开放日时发生异常: " + e.getMessage(), e);
        }
    }
    
    /**
     * 带异常处理的募集期计算方法
     */
    public List<FundPeriod> calculateFundPeriodsWithExceptionHandling(List<LocalDate> openDays,
                                                                    SubscriptionRule rule) {
        try {
            logger.info("调用带异常处理的募集期计算方法");
            return calculateFundPeriods(openDays, rule);
        } catch (IllegalArgumentException e) {
            // 参数异常：直接抛出
            logger.error("参数校验失败: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            // 其他异常：包装后抛出
            logger.error("计算募集期间时发生未知异常", e);
            throw new RuntimeException("计算募集期间时发生异常: " + e.getMessage(), e);
        }
    }

    /**
     * 参数校验
     */
    private void validateParameters(SubscriptionRule rule, LocalDate startDate,
                                  LocalDate endDate, Integer periodCount) {

        logger.debug("开始参数校验");

        // 1. 必选参数校验
        if (rule == null) {
            logger.error("申购规则参数为空");
            throw new IllegalArgumentException("申购规则参数不能为空");
        }
        if (rule.getTradeFrequency() == null) {
            logger.error("申购交易频率为空");
            throw new IllegalArgumentException("申购交易频率不能为空");
        }
        if (rule.getOpenDayIndex() == null || rule.getOpenDayIndex() < 1) {
            logger.error("开放日序号无效: {}", rule.getOpenDayIndex());
            throw new IllegalArgumentException("开放日序号必须大于0");
        }
        if (rule.getCloseDayOffset() == null || rule.getCloseDayOffset() < 0) {
            logger.error("募集期结束日偏移量无效: {}", rule.getCloseDayOffset());
            throw new IllegalArgumentException("募集期结束日偏移量不能为负数");
        }

        // 2. 开放日序号约束校验
        validateOpenDayIndex(rule.getOpenDayIndex(), rule.getTradeFrequency());

        // 3. 日期范围校验
        if (startDate == null) {
            logger.error("开始日期为空");
            throw new IllegalArgumentException("开始日期不能为空");
        }
        if (endDate != null && periodCount != null) {
            logger.error("同时指定了结束日期和期数: endDate={}, periodCount={}", endDate, periodCount);
            throw new IllegalArgumentException("结束日期和期数不能同时指定");
        }
        if (endDate == null && periodCount == null) {
            logger.error("结束日期和期数都未指定");
            throw new IllegalArgumentException("结束日期和期数必须指定其中一个");
        }
        if (endDate != null && endDate.isBefore(startDate)) {
            logger.error("结束日期早于开始日期: startDate={}, endDate={}", startDate, endDate);
            throw new IllegalArgumentException("结束日期不能早于开始日期");
        }
        if (periodCount != null && periodCount < 1) {
            logger.error("期数无效: {}", periodCount);
            throw new IllegalArgumentException("期数必须大于0");
        }

        logger.debug("参数校验通过");
    }

    /**
     * 开放日序号约束校验
     */
    private void validateOpenDayIndex(int openDayIndex, TradeFrequency frequency) {
        int maxIndex;

        switch (frequency) {
            case MONTHLY:
                maxIndex = 31; // 月度最多31天
                break;
            case QUARTERLY:
                maxIndex = 92; // 季度最多92天
                break;
            case SEMI_ANNUAL:
                maxIndex = 184; // 半年最多184天
                break;
            case ANNUAL:
                maxIndex = 366; // 年度最多366天
                break;
            default:
                throw new IllegalArgumentException("不支持的交易频率: " + frequency);
        }

        if (openDayIndex > maxIndex) {
            throw new IllegalArgumentException(
                String.format("开放日序号%d超过%s频率的最大值%d", openDayIndex, frequency, maxIndex));
        }
    }

    /**
     * 确定结束日期
     */
    private LocalDate determineEndDate(LocalDate startDate, LocalDate endDate, Integer periodCount, TradeFrequency frequency) {
        if (endDate != null) {
            return endDate;
        }

        // 根据期数计算结束日期
        LocalDate calculatedEndDate = startDate;
        for (int i = 0; i < periodCount; i++) {
            calculatedEndDate = getNextPeriodStart(calculatedEndDate, frequency);
        }
        return calculatedEndDate.minusDays(1); // 减一天确保不超过最后一个周期
    }

    /**
     * 计算周期范围
     */
    private DateRange calculatePeriodRange(LocalDate periodStart, TradeFrequency frequency) {
        LocalDate periodEnd;

        switch (frequency) {
            case MONTHLY:
                // 月度：当月第一天到最后一天
                LocalDate monthStart = periodStart.withDayOfMonth(1);
                periodEnd = monthStart.plusMonths(1).minusDays(1);
                return new DateRange(monthStart, periodEnd);

            case QUARTERLY:
                // 季度：当季第一天到最后一天
                int quarter = (periodStart.getMonthValue() - 1) / 3;
                LocalDate quarterStart = LocalDate.of(periodStart.getYear(), quarter * 3 + 1, 1);
                periodEnd = quarterStart.plusMonths(3).minusDays(1);
                return new DateRange(quarterStart, periodEnd);

            case SEMI_ANNUAL:
                // 半年：当半年第一天到最后一天
                int halfYear = (periodStart.getMonthValue() - 1) / 6;
                LocalDate halfYearStart = LocalDate.of(periodStart.getYear(), halfYear * 6 + 1, 1);
                periodEnd = halfYearStart.plusMonths(6).minusDays(1);
                return new DateRange(halfYearStart, periodEnd);

            case ANNUAL:
                // 年度：当年第一天到最后一天
                LocalDate yearStart = LocalDate.of(periodStart.getYear(), 1, 1);
                periodEnd = yearStart.plusYears(1).minusDays(1);
                return new DateRange(yearStart, periodEnd);

            default:
                throw new IllegalArgumentException("不支持的交易频率: " + frequency);
        }
    }

    /**
     * 获取下一个周期的开始日期
     */
    private LocalDate getNextPeriodStart(LocalDate currentPeriodStart, TradeFrequency frequency) {
        switch (frequency) {
            case MONTHLY:
                return currentPeriodStart.plusMonths(1);
            case QUARTERLY:
                return currentPeriodStart.plusMonths(3);
            case SEMI_ANNUAL:
                return currentPeriodStart.plusMonths(6);
            case ANNUAL:
                return currentPeriodStart.plusYears(1);
            default:
                throw new IllegalArgumentException("不支持的交易频率: " + frequency);
        }
    }

    /**
     * 计算周期内目标开放日
     */
    private LocalDate calculateTargetOpenDay(DateRange periodRange, SubscriptionRule rule) {
        LocalDate targetDate;

        if (rule.getOpenDayOrder() == OpenDayOrder.POSITIVE) {
            // 正数：从周期第一天开始数第N个目标日
            targetDate = findNthTargetDay(periodRange.getStart(), periodRange.getEnd(),
                                        rule.getOpenDayIndex(), rule.getOpenDayType(), true);
        } else {
            // 倒数：从周期最后一天开始数第N个目标日
            targetDate = findNthTargetDay(periodRange.getEnd(), periodRange.getStart(),
                                        rule.getOpenDayIndex(), rule.getOpenDayType(), false);
        }

        return targetDate;
    }

    /**
     * 查找第N个目标日
     */
    private LocalDate findNthTargetDay(LocalDate startDate, LocalDate endDate, int index,
                                     DayType dayType, boolean forward) {
        LocalDate currentDate = startDate;
        int count = 0;

        while ((forward && !currentDate.isAfter(endDate)) ||
               (!forward && !currentDate.isBefore(endDate))) {

            if (dayType == DayType.NATURAL_DAY ||
                (dayType == DayType.WORKING_DAY && HolidayUtil.isWorkingDay(currentDate))) {
                count++;
                if (count == index) {
                    return currentDate;
                }
            }

            currentDate = forward ? currentDate.plusDays(1) : currentDate.minusDays(1);
        }

        throw new IllegalArgumentException("周期内无法找到第" + index + "个" + dayType + "类型的目标日");
    }

    /**
     * 根据日类型调整开放日
     */
    private LocalDate adjustOpenDayByType(LocalDate targetDate, DateRange periodRange, DayType dayType) {
        if (dayType == DayType.NATURAL_DAY) {
            return targetDate; // 自然日无需调整
        }

        // 工作日需要调整节假日
        if (HolidayUtil.isWorkingDay(targetDate)) {
            return targetDate; // 已是工作日
        }

        // 目标日是节假日，往后调整至下一个工作日
        LocalDate adjustedDate = targetDate.plusDays(1);
        while (!adjustedDate.isAfter(periodRange.getEnd()) && !HolidayUtil.isWorkingDay(adjustedDate)) {
            adjustedDate = adjustedDate.plusDays(1);
        }

        // 如果调整后超过周期范围，往前调整至最后一个工作日
        if (adjustedDate.isAfter(periodRange.getEnd())) {
            adjustedDate = targetDate.minusDays(1);
            while (!adjustedDate.isBefore(periodRange.getStart()) && !HolidayUtil.isWorkingDay(adjustedDate)) {
                adjustedDate = adjustedDate.minusDays(1);
            }

            if (adjustedDate.isBefore(periodRange.getStart())) {
                throw new IllegalArgumentException("周期内无法找到合适的工作日作为开放日");
            }
        }

        return adjustedDate;
    }

    /**
     * 计算募集期结束日时间
     */
    private LocalDateTime calculateCloseDateTime(LocalDate openDay, SubscriptionRule rule) {

        // 1. 计算募集期结束日期
        LocalDate closeDate = calculateCloseDate(openDay, rule.getCloseDayOffset(), rule.getOffsetType());

        // 2. 组合截止时间
        LocalDateTime closeDateTime = LocalDateTime.of(closeDate, rule.getCutoffTime());

        return closeDateTime;
    }

    /**
     * 计算募集期结束日期
     */
    private LocalDate calculateCloseDate(LocalDate openDay, int closeDayOffset, OffsetType offsetType) {

        if (offsetType == OffsetType.NATURAL_DAY) {
            // 自然日偏移：直接减去天数
            return openDay.minusDays(closeDayOffset);
        } else {
            // 工作日偏移：减去指定个数的工作日
            LocalDate currentDate = openDay;
            int remainingDays = closeDayOffset;

            while (remainingDays > 0) {
                currentDate = currentDate.minusDays(1);
                if (HolidayUtil.isWorkingDay(currentDate)) {
                    remainingDays--;
                }
            }

            // 如果结果日期是节假日，往前调整至前一个工作日
            while (!HolidayUtil.isWorkingDay(currentDate)) {
                currentDate = currentDate.minusDays(1);
            }

            return currentDate;
        }
    }

    /**
     * 生成订单归集规则描述
     */
    private String generateRuleDescription(LocalDateTime startDateTime, LocalDateTime closeDateTime, LocalDate openDay) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M.d HH:mm");

        return String.format("%s后→%s前的订单归属于%s",
                            startDateTime.format(formatter),
                            closeDateTime.format(formatter),
                            openDay.format(DateTimeFormatter.ofPattern("M.d")));
    }




}
